import { Injectable, UnauthorizedException } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { JwtService } from "@nestjs/jwt";
import { compare } from "bcrypt";

import { UsersService, UserWithRoles } from "../users/users.service";
import { LoginDto } from "./dto/login.dto";
import { RefreshTokenDto } from "./dto/refresh-token.dto";
import { AuthTokens } from "./interfaces/auth-tokens.interface";
import { JwtPayload } from "./interfaces/jwt-payload.interface";

@Injectable()
export class AuthService {
  private readonly refreshSecret: string;
  private readonly refreshTtl: number;

  constructor(
    private readonly usersService: UsersService,
    private readonly jwtService: JwtService,
    private readonly configService: ConfigService,
  ) {
    this.refreshSecret = this.configService.get<string>("auth.refreshTokenSecret", "");
    this.refreshTtl = this.configService.get<number>("auth.refreshTokenTtl", 604800);

    if (!this.refreshSecret) {
      throw new Error("JWT_REFRESH_TOKEN_SECRET must be defined");
    }
  }

  async login(dto: LoginDto): Promise<AuthTokens> {
    const user = await this.validateUser(dto.identifier, dto.password);

    await this.usersService.updateLastLogin(user.id);

    return this.generateTokens(user);
  }

  async refresh(dto: RefreshTokenDto): Promise<AuthTokens> {
    try {
      const payload = await this.jwtService.verifyAsync<JwtPayload>(dto.refreshToken, {
        secret: this.refreshSecret,
      });

      const user = await this.usersService.findById(payload.sub);
      if (!user) {
        throw new UnauthorizedException("User no longer exists");
      }

      return this.generateTokens(user);
    } catch (error) {
      throw new UnauthorizedException("Invalid refresh token");
    }
  }

  private async validateUser(identifier: string, password: string): Promise<UserWithRoles> {
    const user = await this.usersService.findByIdentifier(identifier);

    if (!user || !user.passwordHash) {
      throw new UnauthorizedException("Invalid credentials");
    }

    const isMatch = await compare(password, user.passwordHash);

    if (!isMatch) {
      throw new UnauthorizedException("Invalid credentials");
    }

    if (!user.isActive) {
      throw new UnauthorizedException("User is disabled");
    }

    return user;
  }

  private async generateTokens(user: UserWithRoles): Promise<AuthTokens> {
    const payload = this.toJwtPayload(user);

    const [accessToken, refreshToken] = await Promise.all([
      this.jwtService.signAsync(payload),
      this.jwtService.signAsync(payload, {
        secret: this.refreshSecret,
        expiresIn: `${this.refreshTtl}s`,
      }),
    ]);

    return {
      accessToken,
      refreshToken,
      user: this.usersService.toRequestUser(user),
    };
  }

  private toJwtPayload(user: UserWithRoles): JwtPayload {
    return {
      sub: user.id,
      email: user.email,
      phone: user.phone,
      displayName: user.displayName,
      roles: user.roles.map((relation) => relation.role.name),
    };
  }
}
